/*
 * Created on 25/mag/07
 *
 * To change the template for this generated file go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
package it.unibo.deis.lia.mmhc.com;

import java.util.*;

import it.unibo.deis.lia.location.*;
import it.unibo.deis.lia.mmhc.mpe.MobilityEstimator;
import it.unibo.deis.lia.mmhc.nip.NetworkInterfaceProvider;
import it.unibo.deis.lia.mmhc.util.*;
import it.unibo.deis.lia.networkInterface.*;

public class ConnectorManager extends Thread {

	private ConnectorEvaluator connectorEvaluator;
	private ConnectorConfigurator connectorConfigurator;

	private Vector<Channel> suitableChannels = new Vector<Channel>();
	private Vector<Connector> suitableConnectors = new Vector<Connector>();
	private Vector<Connector> manualConnections = new Vector<Connector>();

	private static ConnectorManager connectorManager = null;

	public synchronized static ConnectorManager getInstance() {
		if (connectorManager == null) {
			connectorManager = new ConnectorManager();
			connectorManager.start();
		}
		return connectorManager;
	}

	private ConnectorManager() {
		/*
		 * 1) activate connectorEvaluator 2) activate connectorConfigurator
		 */
		BottomMetric metric = new SimpleBottomMetric((float) 0.05);// XXX 0.15
		connectorEvaluator = new ConnectorEvaluator(metric);
		connectorConfigurator = new ConnectorConfigurator();
	}

	public void setMetric(BottomMetric newBottomMetric) {
		connectorEvaluator.setMetric(newBottomMetric);
	}

	public Vector<Channel> getSuitableChannels() {
		return new Vector<Channel>(suitableChannels);
	}

	public Vector<Connector> getSuitableConnectors() {
		return new Vector<Connector>(suitableConnectors);
	}

	/**
	 * Forza il ConnectorManager a connettere l'interfaccia di rete al connector
	 * specificato anziche` al migliore disponibile (rieffettua la connessione
	 * anche se l'interfaccia di rete risulta gia` connessa)
	 * 
	 * @param connector
	 *            : Connector a cui l'interfaccia si dovra` collegare
	 */
	public synchronized void setManualConnection(Connector connector) {
		manualConnections.add(connector);
	}

	public void run() {
		try {
			MobilityEstimator mpe = MobilityEstimator.getInstance(/*
																 * MobilityEstimator.
																 * StandardDevice
																 * ,
																 * MobilityEstimator
																 * .
																 * StandardDevice
																 */);

			Vector<LocationOss> joints = null;
			float mobilityDegree = -1;
			int mobilityState = -1;

			NetworkInterfaceProvider networkInterfaceProvider = NetworkInterfaceProvider
					.getInstance();
			Vector<GenericNetworkInterface> availableInterfaces = networkInterfaceProvider
					.getAvailableInterfaces();

			while (true) {
				try {
					// System.out.println("ConnectorManager: while...");

					joints = mpe.getJoint();
					mobilityDegree = mpe.getMobilityDegree();
					mobilityState = mpe.getMobilityState();

					suitableConnectors = connectorEvaluator
							.getSuitableConnectors(mobilityDegree,
									mobilityState, joints, availableInterfaces);

					// System.out.println("ConnectorManager: suitableConnectors.size() "+suitableConnectors.size());
					// System.out.println("\tsuitableConnectors "+suitableConnectors);

					// gestione manualConnections
					// TODO: togliere il Channel preesistente? O semplicemente
					// aspettare che lo rimuova il ClientRouterManager
					// soprastante?
					for (Connector connector : manualConnections) {
						GenericNetworkInterface gni = connector
								.getLocalNetworkInterface();
						if (gni instanceof IEEE80211) {
							String newIPAddress = ((IEEE80211) gni)
									.connect((BSS) connector
											.getConnectorDescription());
							suitableChannels.addElement(new Channel(connector,
									newIPAddress));
						} else if (gni instanceof Bluetooth) {
							String newIPAddress = ((Bluetooth) gni)
									.connect((RemoteBluetooth) connector
											.getConnectorDescription());
							suitableChannels.addElement(new Channel(connector,
									newIPAddress));
						} else {
							System.err
									.println("Network interface not supported by setManualConnection");
						}
					}
					manualConnections.clear();

					suitableChannels = connectorConfigurator
							.getSuitableChannels(suitableConnectors,
									suitableChannels);
					for (int i = 0; i < availableInterfaces.size(); i++) {
						// System.out.println("ConnectorManager: for...");
						GenericNetworkInterface gni = availableInterfaces
								.elementAt(i);
						// System.out.println("ConnectorManager: evaluating interface #"+i+": "+gni);
						if (gni instanceof Ethernet) {
							Ethernet eth = (Ethernet) gni;
							Connector fakeEthConnector = new Connector(eth,
									null, (float) 0.9, (float) 0.1, false,
									true, 2048);
							fakeEthConnector
									.setEnduranceEstimation((float) 1.0);
							String ip = eth.getLocalAddress();
							// creo il Channel solo se l'interfaccia Ethernet ha
							// un indirizzo IP assegnato
							if (ip != null) {
								Channel ethernetChannel = new Channel(
										fakeEthConnector, ip);
								if (!suitableChannels.contains(ethernetChannel))
									suitableChannels
											.addElement(ethernetChannel);
							}
						}
					}
					System.out.println("ConnectorManager suitableChannels "
							+ suitableChannels);
					/*
					 * if(suitableChannels.size()==0){
					 * mpe.setPhase(MobilityEstimator.SEARCH); } else{
					 * mpe.setPhase(MobilityEstimator.CONNECTED); }
					 */

					sleep(2000); // XXX 5000 should be adequate
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Metodo utilizzato dal RoutingManager, per segnalare che un determinato
	 * Channel non e` piu` utilizzabile
	 * 
	 * @param channel
	 *            : Channel caduto
	 */
	public void channelLost(Channel channel) {
		suitableChannels.remove(channel);
		// TODO: notificare l'osservatore (che sara` il nostro client)
	}
}
